#include "Certificate.hpp"

using namespace BFT::order::statemanagement;

Certificate::Certificate(int _replicas)
{
//	Certificate(_replicas, _my_order_index, false);
	prepareMacs.resize(_replicas);
	commitMacs.resize(_replicas);
	prepareCache.resize(_replicas);
	commitCache.resize(_replicas);
	clear_flag = true;
	takecp = false;
	seqNo = -1;
//	my_order_index = _my_order_index;
}

Certificate::Certificate(int _replicas, bool _cp)
{
	prepareMacs.resize(_replicas);
	commitMacs.resize(_replicas);
	prepareCache.resize(_replicas);
	commitCache.resize(_replicas);
	clear_flag = true;
	takecp = _cp;
	seqNo = -1;
//	my_order_index = _my_order_index;
}

boost::shared_ptr<BFT::order::messages::PrePrepare> Certificate::getCachedPrePrepare()
{
	boost::shared_ptr<BFT::order::messages::PrePrepare> pp = preprepare;
	preprepare.reset();
	return pp;
}

boost::shared_ptr<BFT::messages::HistoryDigest> Certificate::getHistory()
{
	if (certEntry.use_count() == 0)
	{
		boost::shared_ptr<BFT::messages::HistoryDigest> empty;
		return empty;
	}
	else
		return certEntry.get()->getHistoryDigest();
}

boost::shared_ptr<BFT::messages::CommandBatch> Certificate::getCommandBatch()
{
	if (certEntry.use_count() == 0)
	{
		boost::shared_ptr<BFT::messages::CommandBatch> empty;
		return empty;
	}
	else
		return certEntry.get()->getCommandBatch();
}

boost::shared_ptr<BFT::messages::NonDeterminism> Certificate::getNonDeterminism()
{
	if (certEntry.use_count() == 0)
	{
		boost::shared_ptr<BFT::messages::NonDeterminism> empty;
		return empty;
	}
	else
		return certEntry.get()->getNonDeterminism();
}

boost::shared_ptr<BFT::messages::Digest> Certificate::getEntryDigest()
{
	int temp;
	if (entryDigest.use_count() == 0)
		entryDigest.reset( new BFT::messages::Digest(certEntry.get()->getBytes(temp), temp));
		
	return entryDigest;
}

boost::shared_ptr<BFT::messages::NextBatch> Certificate::getNextBatch(boost::shared_ptr<BFT::BaseNode> _bn, uint32_t _current_view)
{
	if (certEntry.use_count() == 0)
	{
		fprintf(stderr, "trying to get a nextbatch from a certificate that has nothing associated with it\n");
		exit(98);
	}
	
	if (isCommitted() && 
	    	(nextBatch.use_count() == 0 || 
	     	 nextBatch.get()->getTag() < COMMITTED_NEXT_BATCH || 
	     	 nextBatch.get()->getView() < _current_view )  )
	{
	 	//create a new nextbatch
	 	nextBatch.reset (new BFT::messages::CommittedNextBatch(_current_view,
	 				seqNo, certEntry, takecp, _bn.get()->getMyOrderIndex()) );

//	 	_bn.get()->authenticateExecMacArrayMessage(nextBatch);
	}
	else
	{
	
		if( isPrepared() &&
			(nextBatch.use_count() == 0 ||
			 nextBatch.get()->getTag() < TENTATIVE_NEXT_BATCH ||
			 nextBatch.get()->getView() < _current_view ) )
		{
	 		//create a new nextbatch
		 	nextBatch.reset (new BFT::messages::TentativeNextBatch(_current_view,
	 				seqNo, certEntry, takecp, _bn.get()->getMyOrderIndex()) );
//		 	_bn.get()->authenticateExecMacArrayMessage(nextBatch);
		
		}
		else
		{
		
			if(nextBatch.use_count() == 0 || nextBatch.get()->getView() < _current_view)
			{	
				nextBatch.reset( new BFT::messages::SpeculativeNextBatch(_current_view,
		 				seqNo, certEntry, takecp, _bn.get()->getMyOrderIndex()) );
//			 	_bn.get()->authenticateExecMacArrayMessage(nextBatch);
			}
		}
	}
	 
	return nextBatch; 
}

/************
TODO
For the moment I am going to assume you discard things if you
dont ahve the previous level filled in.  I'll deal with out of
order processing later.
**********/
void Certificate::setEntry(boost::shared_ptr<BFT::messages::CertificateEntry> _entry, uint32_t _seq)
{
	if (!clear_flag)
	{
		fprintf(stderr, "trying to add a certificate entry to a non clear certificate\n");
		exit(99);
	}
	
	certEntry = _entry;
	seqNo = _seq;
	clear_flag = false;
}


/** Add a preprepare that I sent.

Returns true if the preprepare is successfully added, false
otherwise.

Preprepare is added successfully if there is no existing
prepreparehash.  Side effect is setting the preprepare hash,
nondeterminism, commandbatch, and history for the certificate

**/
bool Certificate::addMyPrePrepare(boost::shared_ptr<BFT::order::messages::PrePrepare> _pp)
{
	if (addOtherPrePrepare(_pp))
	{
		preprepare= _pp;
		return true;
	}
	return false;
}

bool Certificate::addOtherPrePrepare(boost::shared_ptr<BFT::order::messages::PrePrepare> _pp)
{
	if(!isClear())
		return false;
	
	if(certEntry.use_count() != 0)
		return false;
		
	boost::shared_ptr<std::vector<boost::shared_ptr<BFT::messages::RequestCore> > > reqs = 
		_pp.get()->getRequestBatch().get()->getEntries();
	
	boost::shared_ptr<std::vector< boost::shared_ptr<BFT::messages::Entry> > > entries;
	entries.reset( new std::vector< boost::shared_ptr<BFT::messages::Entry> > );
	entries.get()->resize(reqs.get()->size());
	
//	fprintf(stderr, "Certificate::addotherpreprepare says : reqno: %d, entryno: %d\n"
//		,(int)reqs.get()->size(),(int)entries.get()->size());
	
	for(unsigned int i=0; i < reqs.get()->size(); i++)
		entries.get()->at(i) = reqs.get()->at(i).get()->getEntry();
	
	boost::shared_ptr<BFT::messages::CommandBatch> commands ( 
		new BFT::messages::CommandBatch(entries));
	
	boost::shared_ptr<BFT::messages::NonDeterminism> nondet = _pp.get()->getNonDeterminism();
	
	boost::shared_ptr<BFT::messages::HistoryDigest> history (
		new BFT::messages::HistoryDigest(_pp.get()->getHistory(), commands, nondet, _pp.get()->getCPHash()));
	
	certEntry.reset(new BFT::messages::CertificateEntry(history, commands, nondet, _pp->getCPHash()) );
	
	seqNo = _pp.get()->getSeqNo();
	prepareMacs.at(_pp.get()->getSendingReplica()) = true;
	clear_flag = false;
	return true;	
}

bool Certificate::addOtherPrepare(boost::shared_ptr<BFT::order::messages::Prepare> _p)
{
	if (getHistory().use_count() == 0) 
		return false;
	if (!(getHistory().get()->equals( _p.get()->getPrePrepareHash())) )
		return false;
	//TODO ask allen why the java implementation checks both a==b and b==a
	if (prepareMacs.at((int) _p.get()->getSendingReplica() ))
		return false;
	prepareMacs.at( (int) _p.get()->getSendingReplica() ) = true;
	return true;	
}

bool Certificate::addMyCommit(boost::shared_ptr<BFT::order::messages::Commit> _c)
{
	if (commit.use_count() == 0 && addOtherCommit(_c))
	{
		commit = _c;
		return true;
	}
	
	return false;
}

bool Certificate::addOtherCommit(boost::shared_ptr<BFT::order::messages::Commit> _c)
{
	if (getHistory().use_count() == 0)
		return false;
	if (!(getHistory().get()->equals( _c.get()->getPrePrepareHash())))
		return false;
	if (commitMacs.at(_c.get()->getSendingReplica()))	
		return false;
	commitMacs.at(_c.get()->getSendingReplica()) = true;
	return true;
}

void Certificate::forceCommitted()
{
	if (certEntry.use_count() == 0)
	{
		fprintf(stderr,"can only commit something that has a nextbatch\n");
		exit(99);
	}
	
	for (unsigned int i=0; i<commitMacs.size(); i++)
		commitMacs.at(i) = true;	
}

void Certificate::forcePrepared()
{
	if (certEntry.use_count() == 0)
	{
		fprintf(stderr,"can only prepare something that has a nextbatch\n");
		exit(100);
	}
	
	for (unsigned int i=0; i<prepareMacs.size(); i++)
		prepareMacs.at(i) = true;	
}

bool Certificate::isPrepared()
{
	int count = 0;
	int count2 = 0;
	
	for (unsigned int i=0; i < prepareMacs.size(); i++)
	{
		if (prepareMacs.at(i) || commitMacs.at(i))
			count++;
		if (commitMacs.at(i))
			count2++;
	}
	
	//fprintf(stderr, "seqno: %d, count %d, quorumsize %d --- count2 %d, smallquorumsize%d\n",
	//	seqNo, count,getQuorumSize(),count2,getSmallQuorumSize());
	
	return (count >= getQuorumSize() || count2 >= getSmallQuorumSize() );
}

int Certificate::getPreparedCount()
{
	int count = 0;
	
	for (unsigned int i=0; i<prepareMacs.size();i++)
		if(prepareMacs.at(i))
			count++;
	return count;
}

bool Certificate::isCommitted()
{
	int count =0;
	for (unsigned int i=0; i<commitMacs.size();i++)
		if(commitMacs.at(i))
			count++;
	return count >= getQuorumSize();	
}

int Certificate::getFastQuorumSize()
{
	return BFT::Parameters::fastOrderQuorumSize();
}

int Certificate::getQuorumSize()
{
	return BFT::Parameters::largeOrderQuorumSize();
}

int Certificate::getSmallQuorumSize()
{
	return BFT::Parameters::smallOrderQuorumSize();
}

boost::shared_ptr<BFT::order::messages::Prepare> Certificate::getPrepare()
{
	boost::unique_lock<boost::mutex> lock(synchronize);
	return prepare;
}

bool Certificate::addMyPrepare(boost::shared_ptr<BFT::order::messages::Prepare> _p)
{
	boost::unique_lock<boost::mutex> lock(synchronize);
	
	if (prepare.use_count() != 0)
		return false;
		
	if (getHistory().use_count() == 0)
		return false;
		
	if ( !getHistory().get()->equals(_p.get()->getPrePrepareHash()))
		return false;
		
	prepareMacs.at(_p.get()->getSendingReplica()) = true;
	prepare = _p;
	return true;
}


